// Copyright (c) Improbable Worlds Ltd, All Rights Reserved

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Templates/SubclassOf.h"

#include "SpatialInterestConstraints.generated.h"

namespace SpatialGDK
{
struct QueryConstraint;
}
class USpatialClassInfoManager;

/**
 * Query data used to configure Query-based Interest.
 */
USTRUCT(BlueprintType)
struct SPATIALGDK_API FQueryData
{
	GENERATED_BODY()
public:
	FQueryData() = default;
	~FQueryData() = default;

	/**
	 * The root constraint associated with the query generated by this component.
	 */
	UPROPERTY(BlueprintReadonly, EditDefaultsOnly, Instanced, Category = "Query Data")
	class UAbstractQueryConstraint* Constraint;

	/**
	 * Used for frequency-based rate limiting. Represents the maximum frequency
	 * of updates for this particular query. An empty option represents no
	 * rate-limiting (ie. updates are received as soon as possible). Frequency
	 * is measured in Hz.
	 *
	 * If set, the time between consecutive updates will be at least
	 * 1/frequency. This is determined at the time that updates are sent from
	 * the Runtime and may not necessarily correspond to the time updates are
	 * received by the worker.
	 *
	 * If after an update has been sent, multiple updates are applied to a
	 * component, they will be merged and sent as a single update after
	 * 1/frequency of the last sent update. When components with events are
	 * merged, the resultant component will contain a concatenation of all the
	 * events.
	 *
	 * If multiple queries match the same Entity-Component then the highest of
	 * all frequencies is used.
	 */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Meta = (ClampMin = 0.0), Category = "SpatialGDK")
	float Frequency;
};

UCLASS(Abstract, BlueprintInternalUseOnly)
class SPATIALGDK_API UAbstractQueryConstraint : public UObject
{
	GENERATED_BODY()
public:
	UAbstractQueryConstraint() = default;
	virtual ~UAbstractQueryConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const PURE_VIRTUAL(UAbstractQueryConstraint::CreateConstraint, );
};

/**
 * Creates a constraint that is satisfied if any of its inner constraints are satisfied.
 */
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class SPATIALGDK_API UOrConstraint final : public UAbstractQueryConstraint
{
	GENERATED_BODY()
public:
	UOrConstraint() = default;
	~UOrConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const override;

	/** Entities captured by any subconstraints will be included in interest results. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Instanced, Category = "Or Constraint")
	TArray<UAbstractQueryConstraint *> Constraints;
};

/**
 * Creates a constraint that is satisfied if all of its inner constraints are satisfied.
 */
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class SPATIALGDK_API UAndConstraint final : public UAbstractQueryConstraint
{
	GENERATED_BODY()
public:
	UAndConstraint() = default;
	~UAndConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const override;

	/** Entities captured by all subconstraints will be included in interest results. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Instanced, Category = "And Constraint")
	TArray<UAbstractQueryConstraint *> Constraints;
};

/**
 * Creates a constraint that includes all entities within a sphere centered on the specified point.
 */
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class SPATIALGDK_API USphereConstraint final : public UAbstractQueryConstraint
{
	GENERATED_BODY()
public:
	USphereConstraint() = default;
	~USphereConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const override;

	/** The location in the world that this constraint is relative to. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Sphere Constraint")
	FVector Center = FVector::ZeroVector;

	/** The size of the sphere represented by this constraint in centimeters. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Meta = (ClampMin = 0.0), Category = "Sphere Constraint")
	float Radius = 0.0f;
};

/**
 * Creates a constraint that includes all entities within a cylinder centered on the specified point.
 */
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class SPATIALGDK_API UCylinderConstraint final : public UAbstractQueryConstraint
{
	GENERATED_BODY()
public:
	UCylinderConstraint() = default;
	~UCylinderConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const override;

	/** The location in the world that this constraint is relative to. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Cylinder Constraint")
	FVector Center = FVector::ZeroVector;

	/** The size of the cylinder represented by this constraint in centimeters. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Meta = (ClampMin = 0.0), Category = "Cylinder Constraint")
	float Radius = 0.0f;
};

/**
 * Creates a constraint that includes all entities within a bounding box centered on the specified point.
 */
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class SPATIALGDK_API UBoxConstraint final : public UAbstractQueryConstraint
{
	GENERATED_BODY()
public:
	UBoxConstraint() = default;
	~UBoxConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const override;

	/** The location in the world that this constraint is relative to. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Box Constraint")
	FVector Center = FVector::ZeroVector;

	/** The size of the box represented by this constraint. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Meta = (ClampMin = 0.0), Category = "Box Constraint")
	FVector EdgeLengths = FVector::ZeroVector;
};

/**
 * Creates a constraint that includes all entities within a sphere centered on the actor.
 */
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class SPATIALGDK_API URelativeSphereConstraint final : public UAbstractQueryConstraint
{
	GENERATED_BODY()
public:
	URelativeSphereConstraint() = default;
	~URelativeSphereConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const override;

	/** The size of the sphere represented by this constraint in centimeters. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Meta = (ClampMin = 0.0), Category = "Relative Sphere Constraint")
	float Radius = 0.0f;
};

/**
 * Creates a constraint that includes all entities within a cylinder centered on the actor.
 */
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class SPATIALGDK_API URelativeCylinderConstraint final : public UAbstractQueryConstraint
{
	GENERATED_BODY()
public:
	URelativeCylinderConstraint() = default;
	~URelativeCylinderConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const override;

	/** The size of the cylinder represented by this constraint in centimeters. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Meta = (ClampMin = 0.0), Category = "Relative Cylinder Constraint")
	float Radius = 0.0f;
};

/**
 * Creates a constraint that includes all entities within a bounding box centered on the actor.
 */
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class SPATIALGDK_API URelativeBoxConstraint final : public UAbstractQueryConstraint
{
	GENERATED_BODY()
public:
	URelativeBoxConstraint() = default;
	~URelativeBoxConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const override;

	/** The size of the box represented by this constraint. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Meta = (ClampMin = 0.0), Category = "Relative Box Constraint")
	FVector EdgeLengths = FVector::ZeroVector;
};

/**
 * Creates a constraint that includes an actor type (including subtypes) and a cylindrical range around the actor with the interest.
 */
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class SPATIALGDK_API UCheckoutRadiusConstraint final : public UAbstractQueryConstraint
{
	GENERATED_BODY()
public:
	UCheckoutRadiusConstraint() = default;
	~UCheckoutRadiusConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const override;

	/** The base type of actor that this constraint will capture. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Checkout Radius Constraint")
	TSubclassOf<AActor> ActorClass;

	/** The size of the cylinder represented by this constraint in centimeters. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Meta = (ClampMin = 0.0), Category = "Checkout Radius Constraint")
	float Radius = 0.0f;
};

/**
 * Creates a constraint that includes all actors of a type (optionally including subtypes).
 */
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class SPATIALGDK_API UActorClassConstraint final : public UAbstractQueryConstraint
{
	GENERATED_BODY()
public:
	UActorClassConstraint() = default;
	~UActorClassConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const override;

	/** The base type of actor that this constraint will capture. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Actor Class Constraint")
	TSubclassOf<AActor> ActorClass;

	/** Whether this constraint should capture derived types. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Actor Class Constraint")
	bool bIncludeDerivedClasses = true;
};

/**
 * Creates a constraint that includes all components of a type (optionally including subtypes).
 */
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class SPATIALGDK_API UComponentClassConstraint final : public UAbstractQueryConstraint
{
	GENERATED_BODY()
public:
	UComponentClassConstraint() = default;
	~UComponentClassConstraint() = default;

	virtual void CreateConstraint(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::QueryConstraint& OutConstraint) const override;

	/** The base type of component that this constraint will capture. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Component Class Constraint")
	TSubclassOf<UActorComponent> ComponentClass;

	/** Whether this constraint should capture derived types. */
	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Component Class Constraint")
	bool bIncludeDerivedClasses = true;
};
